package jwt

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
	"log"
	"net/http"
	"strings"
	"time"
)

var AccessTokenKey = []byte("%bUWoX;9M76Cxfnu#j+YZ03d1rFS2!5|")
var RefreshTokenKey = []byte("%bUWoX;9M76Cxfnu#j+YZ03d1rFS999|")

type RedisJWTHandler struct {
	signingMethod jwt.SigningMethod
	cmd           redis.Cmdable
	rcExpiration  time.Duration
}

type UserClaims struct {
	jwt.RegisteredClaims
	Uid       int64
	UserAgent string
	Ssid      string
}

type RefreshClaims struct {
	jwt.RegisteredClaims
	Uid  int64
	Ssid string
}

func NewRedisJWTHandler(cmd redis.Cmdable) Handler {
	return &RedisJWTHandler{
		cmd:           cmd,
		signingMethod: jwt.SigningMethodHS512,
		rcExpiration:  time.Hour * 24 * 7,
	}
}

// ExtractToken 根据约定 放在Authorization里 Bearer xxx
func (h *RedisJWTHandler) ExtractToken(ctx *gin.Context) string {
	authCode := ctx.GetHeader("Authorization")
	if len(authCode) == 0 {
		//没登陆 没token Authorization这个token没有
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return authCode
	}
	authSegments := strings.Split(authCode, " ")
	if len(authSegments) != 2 {
		//没登陆 Authorization的内容不对
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return ""
	}
	tokenStr := authSegments[1]
	return tokenStr
}

func (h *RedisJWTHandler) SetLoginToken(ctx *gin.Context, uid int64) error {
	ssid := uuid.New().String()
	err := h.setRefreshToken(ctx, uid, ssid)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return err
	}
	return h.SetJWTToken(ctx, uid, ssid)
}

func (h *RedisJWTHandler) SetJWTToken(ctx *gin.Context, uid int64, ssid string) error {
	//调用/refresh_token的时候重新把RefreshToken生成了一遍 可以这么写
	//err := h.setRefreshToken(ctx, uid)
	//if err != nil {
	//	ctx.String(http.StatusOK, "系统错误")
	//	return
	//}
	token := jwt.NewWithClaims(h.signingMethod, UserClaims{
		Uid: uid,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 30)),
		},
		Ssid:      ssid,
		UserAgent: ctx.GetHeader("User-Agent"),
	})
	tokenStr, err := token.SignedString(AccessTokenKey)
	log.Printf("token str %v", tokenStr)
	if err != nil {
		ctx.String(http.StatusOK, "系统异常")
		return err
	}
	ctx.Header("x-jwt-token", tokenStr)
	return nil
}

func (h *RedisJWTHandler) setRefreshToken(ctx *gin.Context, uid int64, ssid string) error {
	token := jwt.NewWithClaims(h.signingMethod, RefreshClaims{
		Uid: uid,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(h.rcExpiration)),
		},
		Ssid: ssid,
	})
	tokenStr, err := token.SignedString(RefreshTokenKey)
	log.Printf("setRefreshToken token str %v", tokenStr)
	if err != nil {
		ctx.String(http.StatusOK, "系统异常")
		return err
	}
	ctx.Header("x-refresh-token", tokenStr)
	return nil
}

func (h *RedisJWTHandler) ClearToken(ctx *gin.Context) error {
	ctx.Header("x-refresh-token", "")
	ctx.Header("x-jwt-token", "")
	uc := ctx.MustGet("user").(UserClaims)
	return h.cmd.Set(ctx, fmt.Sprintf("users:ssid:%s", uc.Ssid), "", h.rcExpiration).Err()
}
func (h *RedisJWTHandler) CheckSession(ctx *gin.Context, ssid string) error {
	cnt, err := h.cmd.Exists(ctx, fmt.Sprintf("users:ssid:%s", ssid)).Result()
	if err != nil {
		return err
	}
	if cnt > 0 {
		return errors.New("token 无效")
	}
	//看ssid是否过期 过期表示退出登录的状态
	//err := m.CheckSession(ctx, fmt.Sprintf("users:ssid:%s", uc.Ssid))
	//if err != nil || cnt > 0 { //token无效或是redis有问题
	//	ctx.AbortWithStatus(http.StatusUnauthorized)
	//	return
	//}
	//可以兼容redis异常的情况 redis崩溃后最好是支持继续访问网站 用这个 做好监控是否有error
	//if cnt>0 { //token无效或是redis有问题 降级策略
	//	ctx.AbortWithStatus(http.StatusUnauthorized)
	//	return
	//}
	return nil
}
